package controlSoftware.GUI;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import controlSoftware.DataProcess.SysModel;
import controlSoftware.GUI.MapObject.MapObjType;
import controlSoftware.util.Common;


public class Map implements ImageObserver{
	
	private static int padding = 20; // create border around the map

	private static MapObject [] mapObjList;
	
	final private static String mapSchemaFilepath = "xml/mapSchema.xsd";
	
	public static int x_loc;

	public static int y_loc;
		
	private boolean isParsed = false;
	
	public static int map_xrange_high = 0;
	public static int map_xrange_low = mainUI.mainCanvas.getWidth();
	public static int map_yrange_high = 0;
	public static int map_yrange_low = mainUI.mainCanvas.getHeight();

	
	/**
	 * Parses xml on generate
	 * @param filepath
	 */
	public Map(String filepath)
	{
		//generate wall objects array
		parseXML(filepath);
	}
	
	/**
	 * Parses the map xml file to get map object attributes
	 * @param filepath
	 */
	private void parseXML(String filepath)
	{
		try {
			File fXmlFile = new File(filepath);
			
			SchemaFactory factory = SchemaFactory
					.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
			Schema schema = factory.newSchema(new File(mapSchemaFilepath));
			Validator validator = schema.newValidator();
			
			DocumentBuilderFactory builderFactory  = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = builderFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);
			
	        validator.validate(new DOMSource(doc));
			doc.getDocumentElement().normalize();

			SysModel.definedMapRatio = Float.valueOf(doc.getDocumentElement().getAttribute("ratio"));
			
			// parses for map objects and stores into MapObject array
			NodeList nBorderList = doc.getElementsByTagName("object");
			mapObjList = new MapObject [nBorderList.getLength()];

			for (int i = 0; i < nBorderList.getLength(); i++) {
				   
				Node nNode = nBorderList.item(i);

				if (nNode.getNodeType() == Node.ELEMENT_NODE) 
				{
					Element object = (Element) nNode;
					// create a new MapObject object
					mapObjList[i] = new MapObject(MapObjType.valueOf(object.getAttribute("type").toUpperCase()));			
					
					// find child nodes of object
					NodeList nNodeList = object.getElementsByTagName("node");
				   
					// Each MapObject may have multiple nodes
					// get x and y values of the node(s) associated with the object
					for (int j = 0; j < nNodeList.getLength(); j++)
					{					
						Node nnNode = nNodeList.item(j);

						if (nnNode.getNodeType() == Node.ELEMENT_NODE) 
						{			 
							Element eElement = (Element) nnNode;
							int x = Integer.valueOf(eElement.getAttribute("x"));
							int y = Integer.valueOf(eElement.getAttribute("y"));
							mapObjList[i].insertNode(x, y);			      
													
							// update map item range on canvas - for centering purposes
							if (x > Map.map_xrange_high)
								this.map_xrange_high = x;
							else if (x < this.map_xrange_low)
								this.map_xrange_low = x;
							
							if (y > Map.map_yrange_high)
								this.map_yrange_high = y;
							else if (y < this.map_yrange_low)
								this.map_yrange_low = y;							
						
						}
					}
					
				}
			}
			
			this.isParsed = true;
		}
		catch (Exception e) {
			System.out.println("Error encountered during map.xml parsing" + e.getMessage());
			this.isParsed = false;
		}
	}
	
	/**
	 * Renders the map onto the canvas
	 */
	public void renderMap()
	{	

		MapNode current;
		int map_xrange = this.map_xrange_high - this.map_xrange_low;
		int map_yrange = this.map_yrange_high - this.map_yrange_low;
		
		// get correct display ratio
		float compression_ratio = (float)(mainUI.mapScaleSlider.getValue())/SysModel.definedMapRatio;
		SysModel.compression_ratio = compression_ratio;	
		
		//int x_offset = 0;
		//int y_offset = 0;
		int x_offset = (int)(SysModel.mapCenter_x - map_xrange*SysModel.compression_ratio/2);
		int y_offset = (int)(SysModel.mapCenter_y - map_yrange*SysModel.compression_ratio/2);
		
		// set up rendering surface
		Graphics g = mainUI.mainCanvas.getGraphics();
		g.clearRect(0, 0, mainUI.mainCanvas.getWidth(), mainUI.mainCanvas.getHeight());
		

		
		// loop through map object array to render every map object
		for (int i = 0; i < mapObjList.length; i ++ ){
			
			// render a region object, which is a polygon specified by the nodes
			if(mapObjList[i].getType() == MapObjType.REGION)
			{ 
				int []xloc = new int [mapObjList[i].getNumOfNodes()];
				int []yloc = new int [mapObjList[i].getNumOfNodes()];
				
				current = mapObjList[i].getNodeHead();
				int j=0; 
				while (current!= null)
				{
					xloc[j] = (int)(current.getX() * compression_ratio + x_offset);
					yloc[j] = (int)(current.getY() * compression_ratio + y_offset);
					current = current.nextMapNode;
					j++;
				}
				     			
				g.drawPolygon(xloc, yloc, mapObjList[i].getNumOfNodes());
				
			}
			
			// renders a calibration station
			if(mapObjList[i].getType() == MapObjType.CALIBRATION)
			{
				
				current = mapObjList[i].getNodeHead();
				x_loc = (int)(current.getX() * compression_ratio + x_offset);
				y_loc = (int)(current.getY() * compression_ratio + y_offset);
				SysModel.initCoordinate.setX(x_loc);
				SysModel.initCoordinate.setY(y_loc);
				
				int width = (int)(10*compression_ratio);
				//System.out.println("calibration ->" + x_loc + ", " + y_loc);
				g.fillOval(x_loc, y_loc, width, width);

			}
			
		}
		
		drawMapScale(mainUI.mapScaleSlider.getValue());
		
	}


	/**
	 * Draws the map scale on the sub canvas
	 * For better visual, the scale does not exceeds 200pxs 
	 * @param px_per_m
	 */
	private static void drawMapScale(int px_per_m) {

		float length = 100; // length in cm
		float render_length = px_per_m;
		Graphics g = mainUI.subCanvas.getGraphics();
		g.clearRect(0, 0, mainUI.subCanvas.getWidth(), mainUI.subCanvas.getHeight());
		
		while (render_length > (float)200)
		{
			// reduce length by half
			render_length = render_length*1/2;
			length = length*1/2;
		}
		
		while (render_length < 50)
		{
			// expand the length by two
			render_length = render_length * 2;
			length = length *2;
		}
		
		// render line
		g.drawLine(getPadding(), mainUI.subCanvas.getHeight()- getPadding() -5, getPadding(), mainUI.subCanvas.getHeight()- getPadding());
		g.drawLine(getPadding() + (int)render_length, mainUI.subCanvas.getHeight()- getPadding()-5, getPadding() + (int)render_length , mainUI.subCanvas.getHeight()- getPadding());
		g.drawLine(getPadding(), mainUI.subCanvas.getHeight()- getPadding(), getPadding() + (int)render_length , mainUI.subCanvas.getHeight()- getPadding());
		
		// render text
		g.drawString("0", getPadding(), mainUI.subCanvas.getHeight()- getPadding());
		if (length >= 100)
		{
			g.drawString("1m", getPadding() + (int)render_length, mainUI.subCanvas.getHeight()- getPadding());
		}
		else
		{
			g.drawString((int)length + " cm", getPadding() + (int)render_length, mainUI.subCanvas.getHeight()- getPadding());
		}
			
	}

	public boolean isParsed() {
		return isParsed;
	}

	public void setParsed(boolean isParsed) {
		this.isParsed = isParsed;
	}

	@Override
	public boolean imageUpdate(Image img, int infoflags, int x, int y,
			int width, int height) {
		// TODO Auto-generated method stub
		return false;
	}

	public static void setPadding(int padding) {
		Map.padding = padding;
	}

	public static int getPadding() {
		return padding;
	}
	
}
